/**
 * For Similar Pairs, displays alignment of a pair in nucleotide and amino acid
 * Extends AlignmentPanelBase 
 */
package jpave.query_interface;

import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.Color;

import java.util.TreeSet;
import java.util.Vector;

import jpave.dataholders.AlignmentData;
import jpave.dataholders.SequenceData;
import jpave.dataholders.AAStatistics;
import java.awt.Graphics2D;

import util.ui.MultilineTextPanel;

public class MainPairAlignPanel extends MainAlignPanel
{
	// gets called first with the nucleotide alignment, then the AA alignment
	// Called from MainToolBarPanel.createPairwisePanel
	private void deprt(String msg) { System.err.println("MainAlignPanel " + msg);}
	Color background1 = new Color(0xEEFFEE);
	Color background2 = new Color(0xEEFFFF);
	
	public MainPairAlignPanel ( int num, AlignmentData align, boolean alt,
			int nInTopGap, int nInBottomGap, int nInLeftGap, int nInRightGap )
	{		
		super ( JPaveFrame.getTextFont() );
		Color c;
		numAlign = num;
		
		if (alt) c = background1; 
		else c = background2;
		
		super.setBackground( c );
		super.setLayout( null );
		dSequenceStart = Math.max( super.getTextWidth( align.getDisplayStr1() ), 
									super.getTextWidth( align.getDisplayStr2() ) );
		dSequenceStart += nInsetGap * 2.0d + nInLeftGap;
		super.setMinPixelX ( dSequenceStart );
		
		boolean isSeq = super.isDrawSeqMode();
		super.setIndexRange ( align.getLowIndex(isSeq), align.getHighIndex(isSeq) );
		super.setCtgDisplay(false);

		alignData = align;
		seqData1 = alignData.getSequence1();
		seqData2 = alignData.getSequence2();
		nTopGap = nInTopGap;
		nBottomGap = nInBottomGap;
		nRightGap = nInRightGap;
		nLeftGap = nInLeftGap;		

		if (seqData1.isDNA()) isDNA=true;
		else isDNA=false;
		
		dFrameLeftX = nInLeftGap;
		dFrameTopY = nInTopGap;
		dRulerHeight = super.getTextWidth( "999999" );
	}
	
	public void refreshPanels ( ) 
	{ 
		selectionBox = new Rectangle2D.Double ( dFrameLeftX, dFrameTopY,  getFrameWidth (), dFrameHeight );
		setupCodingPanels ();	
	};
	
	//---------------------------------JPanel Over-rides-------------------------------------//

	public void paintComponent(Graphics g)
	{
		Graphics2D g2 = (Graphics2D)g;
		super.paintComponent( g );
		
		// Draw the outside frame and divide
		Rectangle2D outsideBox = new Rectangle2D.Double ( dFrameLeftX, dFrameTopY, 
				getFrameWidth (), dFrameHeight );
		Rectangle2D highlightBox = new Rectangle2D.Double ( dFrameLeftX - 2, dFrameTopY - 2, 
				getFrameWidth () + 4, dFrameHeight + 4 );
		
		if ( hasSelection() )
		{
			// Outline whole thing
			Stroke oldStroke = g2.getStroke();
			g2.setColor( selectColor );
			g2.setStroke( new BasicStroke (3) );
			g2.draw( highlightBox );
			g2.setStroke ( oldStroke );
			
			if ( bSeq1Selected )
				g2.fill( new Rectangle2D.Double( dFrameLeftX, dSelectionRow1Top, 
						getFrameRight ( ) - dFrameLeftX, dSelectionMid - dSelectionRow1Top ) );		

			if ( bSeq2Selected )
				g2.fill( new Rectangle2D.Double( dFrameLeftX, dSelectionMid, 
						getFrameRight ( ) - dFrameLeftX, dSelectionRow2Bottom - dSelectionMid ) );					
			
			g2.setColor( Color.BLACK );
		}
			
		g2.setColor( Color.BLACK );
		g2.draw ( outsideBox );
		g2.draw( new Line2D.Double( dFrameLeftX, dDivider1Y, getFrameRight ( ), dDivider1Y ) );
		
		super.drawRuler( g2, 0, dRulerTop, dRulerBottom );
		
		// DRAW Names on Left
		super.drawText( g2, alignData.getDisplayStr1(), dFrameLeftX + nInsetGap, dSeq1Top );
		super.drawSequence ( g2, seqData1, dSeq1Top, dSeq1Bottom );
		
		super.drawText( g2, alignData.getDisplayStr2(), dFrameLeftX + nInsetGap, dSeq2Top );
		super.drawSequence ( g2, seqData2, dSeq2Top, dSeq2Bottom );	
	}
	
	public Dimension getMaximumSize()
	{
		return new Dimension ( Integer.MAX_VALUE, (int)getPreferredSize ().getHeight() );
	}
	
	public Dimension getPreferredSize()
	{
		int nWidth = nLeftGap + (int)getFrameWidth ( ) + nRightGap;
		int nHeight = nTopGap + (int)dFrameHeight + nBottomGap;
		return new Dimension( nWidth, nHeight ); 
	}
	
	//---------------------------------AlignmentPanelBase Over-rides-------------------------------------//
	
	protected boolean getOverHang ( SequenceData seq, int nPos ) 
	{ 	
		char x1 = seqData1.safeGetBaseAt( nPos );
		char x2 = seqData2.safeGetBaseAt( nPos );
		
		if (x1 == ' ' || x2 == ' ') return true; // space is no base

		return false;
	}
	
	protected boolean getIsStop ( SequenceData seq, int nPos ) // 
	{   // CAS 17aug10 - they get changed to upper case in safeGetBaseAt

		if (isDNA) return false;
		
		if (seq == seqData1 && seqData1.safeGetBaseAt( nPos ) == '*') return true;
		if (seq	 == seqData2 && seqData2.safeGetBaseAt( nPos ) == '*') return true;

		return false;
	}
	
	protected boolean getIsNAt ( SequenceData seq, int nPos ) // ambiguous
	{   // CAS 17aug10 - they get changed to upper case in safeGetBaseAt
		char x = 'X';
		if (isDNA) x = 'N';
		char c1 = seqData1.safeGetBaseAt( nPos );
		char c2 = seqData2.safeGetBaseAt( nPos );
		
		if (seq == seqData1 && (c1 == x || c2 == ' ')) return true;
		if (seq == seqData2 && (c2 == x || c1 == ' ')) return true;
		return false;
	}
	
	protected boolean getIsGapAt ( SequenceData seq, int nPos ) // only green on gap strand
	{ 
		if (seq == seqData1 && seqData1.safeGetBaseAt( nPos ) == '.') return true;
		if (seq == seqData2 && seqData2.safeGetBaseAt( nPos ) == '.') return true;
		return false;
	}
	
	protected boolean getIsMismatchAt ( SequenceData seq, int nPos ) 
	{ 	
		char x = 'X';
		if (isDNA) x = 'N';
		char x1 = seqData1.safeGetBaseAt( nPos );
		char x2 = seqData2.safeGetBaseAt( nPos );
		
		if (x1 == ' ' || x1 == '.' || x1 == x) return false; // space is no base
		if (x2 == ' ' || x2 == '.' || x2 == x) return false; // 

		return x1 != x2; // mis both strands
	}
	
	protected boolean getIsSubstitutionAt ( SequenceData seq, int nPos )         // sub both strands
	{ 
		return !isDNA 
				&& seqData1.safeGetBaseAt( nPos ) != seqData2.safeGetBaseAt( nPos )
				&& AAStatistics.isCommonAcidSub( seqData1.safeGetBaseAt( nPos ), seqData2.safeGetBaseAt( nPos ) ); 
	}
	
	public void setDrawMode(int mode) {
		super.setDrawMode(mode);
		boolean isSeq = super.isDrawSeqMode();
		super.setIndexRange ( alignData.getLowIndex(isSeq), alignData.getHighIndex(isSeq) );
  		super.setMinPixelX ( dSequenceStart );
	}
	
	public void changeDraw()
  	{
		super.changeDraw ();
  		super.setMinPixelX ( dSequenceStart );
  	}
	
	public void setBasesPerPixel ( int nBasesPerPixel ) throws Exception
	{
		super.setBasesPerPixel( nBasesPerPixel );
		super.setMinPixelX ( dSequenceStart );
		
		if ( headerPanel != null )
			remove ( headerPanel );
		
		headerPanel = new MultilineTextPanel ( JPaveFrame.getTextFont(), 
						alignData.getDescription(numAlign), 
						nInsetGap, (int)getFrameWidth ( ) - 2, 1); 
		headerPanel.setBackground( Color.WHITE );  
		add ( headerPanel );  
        headerPanel.setLocation( nLeftGap + 1, nTopGap + 1 );		
        dHeaderHeight = headerPanel.getHeight() + 1;
		dFrameHeight = dHeaderHeight 						// Header
					+ nInsetGap * 2.0d + nRowHeight * 2.0d + // Sequences
					+ nInsetGap * 2.0d + dRulerHeight; 		// Ruler
		dDivider1Y = dFrameTopY + dHeaderHeight;
		dRulerTop = dDivider1Y + nInsetGap;
		dRulerBottom = dRulerTop + dRulerHeight;
		dSeq1Top = dRulerBottom + nInsetGap;
		dSeq1Bottom = dSeq1Top + nRowHeight;
		dSeq2Top = dSeq1Bottom + nInsetGap;
		dSeq2Bottom = dSeq2Top + nRowHeight;
		dSelectionRow1Top = dSeq1Top - nInsetGap / 2.0;
		dSelectionMid = dSeq1Bottom + nInsetGap / 2.0;
		dSelectionRow2Bottom = dSeq2Bottom + nInsetGap;
	}
	
	public double getGraphicalDeadWidth ( )
	{
		// The total width of what can't be used for drawing the bases in graphical mode
		return dSequenceStart + nInsetGap;
	}
	
	private double getFrameWidth ( )
	{
		double dWidth = dSequenceStart + getSequenceWidth ( ) + nInsetGap;
		return Math.max ( 700, dWidth );
	}
	
	protected double getFrameLeft ( )
	{
		return dFrameLeftX;
	}
	
	protected double getFrameRight ( )
	{
		return dFrameLeftX + getFrameWidth ( );
	}
	
	public void selectAll () { bSeq1Selected = bSeq2Selected = true; repaint (); };
	
	public void selectNone () {	bSeq1Selected = bSeq2Selected = false; repaint ();};
	
	public boolean hasSelection () { return bSeq1Selected || bSeq2Selected; };
	
	// called for MultiAlignmentPaenel.alignSelectedESTs
	public Vector<String> getContigIDs ( ) 
	{		
		Vector<String> out = new Vector<String>();
		out.add(alignData.getName1());
		out.add(alignData.getName2());
		
		return out;
	}
	
	public void getSelectedContigIDs ( TreeSet<String> set ) 
	{
		if ( alignData.getName1() != null && bSeq1Selected )
			set.add( alignData.getName1() );
		if ( alignData.getName2() != null && bSeq2Selected )
			set.add( alignData.getName2() );		
	};
	
	public void setSelectedContigIDs ( TreeSet<String> set ) 
	{ 
		selectNone ();  // Clear old selections
		
		if ( alignData.getName1() != null && set.contains( alignData.getName1() ) )
			bSeq1Selected = true;
		if ( alignData.getName2() != null && set.contains( alignData.getName2() ) )
			bSeq2Selected = true;		
	};
	
	public void handleClick( MouseEvent e, Point localPoint )
	{
        if ( selectionBox.contains(localPoint) )
        {       	
            // Determine the row for the click.
    		if ( dSelectionRow1Top <= localPoint.y && localPoint.y <= dSelectionRow2Bottom )
    		{
    			// Clear all selected rows if neither shift nor control are down
    			if ( !e.isControlDown() && !e.isShiftDown() )
    				selectNone();
    			
    			// determine the newly selected sequence and toggle it
    			if ( dSelectionRow1Top <= localPoint.y && localPoint.y <= dSelectionMid )
    				bSeq1Selected = !bSeq1Selected;
    			else if ( dSelectionMid <= localPoint.y && localPoint.y <= dSelectionRow2Bottom )
    				bSeq2Selected = !bSeq2Selected;
    		}
    		else
    		{	
                // Click is inside the box, but not within any of the sequences (i.e. in the header)
    			if ( ( e.isControlDown() || e.isShiftDown() ) && hasSelection () )
    				selectNone ();
    			else
    				// Select whole contig
    				selectAll ();
            }
        }
        else if ( !e.isControlDown() && !e.isShiftDown() )
            // Click outside of the main box, clear everything unless shift or control is down 
            selectNone ();                
		
		repaint();
	}
	
	//---------------------------------Private Methods-------------------------------------//
	
	// drawn in AlignmentPanelBase.setupCodingPanels
	private void setupCodingPanels ( )
	{
		// Setup for first sequence (if it isn't a protein)
		// CAS 4/1/11 if ( !alignData.isProteinAlignment() )
		//{
			super.setupCodingPanels ( alignData.getORFCoding1 (),
										alignData.getProteinCoding1 (),
										dSeq1Top - nInsetGap / 2.0,
										dSeq1Bottom + nInsetGap / 2.0 );
		//}
		
		// Setup for the second
		super.setupCodingPanels ( alignData.getORFCoding2 (),
									alignData.getProteinCoding2 (),
									dSeq2Top - nInsetGap / 2.0,
									dSeq2Bottom + nInsetGap );
	}
	

	private SequenceData seqData1 = null;
	private SequenceData seqData2 = null;
	private AlignmentData alignData = null;
	private boolean isDNA = true;
	private int numAlign = 0;
	
    MultilineTextPanel headerPanel;

	boolean bSeq1Selected = false;
	boolean bSeq2Selected = false;	
	Rectangle2D selectionBox = new Rectangle2D.Double (0,0,0,0);
	
	// Attributes for where to draw
	private int nTopGap = 0;
	private int nBottomGap = 0;
	private int nRightGap = 0;
	private int nLeftGap = 0;
	private double dFrameLeftX = 0;
	private double dFrameTopY = 0;
	private double dFrameHeight = 0;
	private double dDivider1Y = 0;
	private double dRulerTop = 0;
	private double dRulerBottom = 0;
	private double dSeq1Top = 0;
	private double dSeq1Bottom = 0;
	private double dSeq2Top = 0;
	private double dSeq2Bottom = 0;
	private double dSelectionRow1Top = 0;
	private double dSelectionMid = 0;
	private double dSelectionRow2Bottom = 0;
	
	private double dHeaderHeight = 0;
	private double dRulerHeight = 0;
	private double dSequenceStart = 0;
	static private final int nInsetGap = 5;
	static private final int nRowHeight = 15;
	
    private static final long serialVersionUID = 1;
}
